// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: xguo

#pragma once

#include <iostream>
#include <map>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <vector>

#include "coin2/exchange/base/symbology/derivative_util.h"
#include "coin2/exchange/base/symbology/futures_product.h"
#include "coin2/exchange/deribit_futures/symbology/currency.h"

namespace coin2::exchange::deribit_futures::symbology {

using deribit_futures::symbology::DeribitFuturesCurrency;

static std::map<std::string, std::string> MONTHS = {
    {"JAN", "01"}, {"FEB", "02"}, {"MAR", "03"}, {"APR", "04"}, {"MAY", "05"}, {"JUN", "06"},
    {"JUL", "07"}, {"AUG", "08"}, {"SEP", "09"}, {"OCT", "10"}, {"NOV", "11"}, {"DEC", "12"},
    {"01", "JAN"}, {"02", "FEB"}, {"03", "MAR"}, {"04", "APR"}, {"05", "MAY"}, {"06", "JUN"},
    {"07", "JUL"}, {"08", "AUG"}, {"09", "SEP"}, {"10", "OCT"}, {"11", "NOV"}, {"12", "DEC"}};

class DeribitFuturesProduct
    : public base::symbology::FuturesProduct<DeribitFuturesCurrency, DeribitFuturesProduct>,
      public base::symbology::ProductFromStrMixin<DeribitFuturesProduct> {
 public:
  using FuturesProduct =
      base::symbology::FuturesProduct<DeribitFuturesCurrency, DeribitFuturesProduct>;
  using ContractType = base::symbology::ContractType;
  using MarketType = base::market::MarketType;

 public:
  DeribitFuturesProduct(
      const DeribitFuturesCurrency& base,
      const DeribitFuturesCurrency& quote,
      const std::string& native_product,
      const std::string& native_subscription_symbol,
      const ContractType contract_type,
      const int64_t expiry_timestamp,
      const int64_t resolve_timestamp)
      : FuturesProduct(
            base,
            quote,
            native_product,
            native_subscription_symbol,
            base::market::ExchangeType::Deribit,
            contract_type,
            expiry_timestamp,
            resolve_timestamp) {}

 public:
  static std::unique_ptr<DeribitFuturesProduct> FromNativeStrImpl(
      const std::string& native_product,
      const int64_t resolve_timestamp) {
    using base::symbology::IsDateStr;

    std::string expiry_str;
    std::unique_ptr<DeribitFuturesCurrency> base;
    std::unique_ptr<DeribitFuturesCurrency> quote;
    std::vector<std::string> splitted;
    splitted = Split(native_product, "-");
    if (splitted.size() != 2) return {};

    expiry_str = splitted[1];
    if (expiry_str != "PERPETUAL") {
      if (!DateStrFromNative(expiry_str, &expiry_str)) return {};
    }
    std::string base_str = splitted[0];
    std::string quote_str = "USD";
    if (expiry_str == "PERPETUAL") {
      std::vector<std::string> currency_pair;
      currency_pair = Split(splitted[0], "_");
      if (currency_pair.size() == 2) {
        base_str = currency_pair[0];
        quote_str = currency_pair[1];
      }
    }
    base = DeribitFuturesCurrency::FromNativeStrNoThrow(base_str, resolve_timestamp);
    quote = DeribitFuturesCurrency::FromNativeStrNoThrow(quote_str, resolve_timestamp);

    if (!base) return {};

    std::string native_product_tmp;
    std::string native_subscription_symbol;
    ContractType contract_type;
    int64_t expiry_timestamp;
    bool success = CalculateParams(
        *base,
        *quote,
        expiry_str,
        resolve_timestamp,
        &native_product_tmp,
        &native_subscription_symbol,
        &contract_type,
        &expiry_timestamp);
    if (success) {
      if (IsDateStr(expiry_str)) {
        DCHECK_EQ(native_product_tmp, native_product);
      }
      return std::make_unique<DeribitFuturesProduct>(
          *base,
          *quote,
          native_product,
          native_subscription_symbol,
          contract_type,
          expiry_timestamp,
          resolve_timestamp);
    } else {
      return {};
    }
  }

  static bool CalculateParams(
      const DeribitFuturesCurrency& base,
      const DeribitFuturesCurrency& quote,
      const std::string& expiry_str,
      const int64_t resolve_timestamp,
      std::string* native_product,
      std::string* native_subscription_symbol,
      ContractType* contract_type,
      int64_t* expiry_timestamp) {
    using base::symbology::ContractType;
    using base::symbology::ExchangeType;
    using base::symbology::FindExpiryFriday;
    using base::symbology::IsDateStr;
    using base::symbology::StringToContractType;
    using base::symbology::ToDateStr;

    static const int64_t expiry_time_of_day_nanosec = 8 * 3600 * 1e9;
    std::stringstream ss;

    // native_product
    ss.str(std::string());
    if (IsDateStr(expiry_str)) {
      bool found = false;
      // If week/month and quarter has same expiries, week/month is not added
      for (auto type :
           {ContractType::QUARTER,
            ContractType::NEXT_QUARTER,
            ContractType::NNEXT_QUARTER,
            ContractType::NNNEXT_QUARTER,
            ContractType::MONTH,
            ContractType::NEXT_MONTH,
            ContractType::WEEK,
            ContractType::NEXT_WEEK}) {
        auto timestamp = FindExpiryFriday(
            ExchangeType::Deribit,
            type,
            resolve_timestamp,
            expiry_time_of_day_nanosec);
        if (expiry_str == ToDateStr(timestamp)) {
          *contract_type = type;
          *expiry_timestamp = timestamp;

          std::string native_expiry_str;
          DateStrToNative(expiry_str, &native_expiry_str);
          ss << base.native_currency() << "-" << native_expiry_str;
          found = true;
          break;
        }
      }
      if (!found) {
        return false;
      }
    } else {
      auto type = StringToContractType(expiry_str);
      if (type) {
        *contract_type = type.value();
        if (*contract_type == ContractType::PERPETUAL) {
          *expiry_timestamp = FuturesProduct::PERPETUAL_TIMESTAMP;
          if (quote.currency() == "USD") {
            ss << base.native_currency(MarketType::Futures) << "-PERPETUAL";
          } else if (quote.currency() == "USDC") {
            ss << base.native_currency(MarketType::Futures) << "_"
               << quote.native_currency(MarketType::Futures) << "-PERPETUAL";
          } else {
            return false;
          }
        } else {
          *expiry_timestamp = FindExpiryFriday(
              ExchangeType::Deribit,
              *contract_type,
              resolve_timestamp,
              expiry_time_of_day_nanosec);
          std::string date_str = ToDateStr(*expiry_timestamp);
          std::string native_date_str;
          DateStrToNative(date_str, &native_date_str);
          ss << base.native_currency() << "-" << native_date_str;
        }
      } else {
        return false;
      }
    }
    *native_product = ss.str();
    *native_subscription_symbol = *native_product;

    return true;
  }

  // native expiry format: 2JUL21 24SEP21
  static bool DateStrToNative(const std::string& date_str, std::string* expriry_str) {
    std::string year = date_str.substr(2, 2);
    std::string month = date_str.substr(4, 2);
    month = MONTHS.at(month);
    std::string day = date_str.substr(6, 2);
    if (day[0] == '0') {
      day = day[1];
    }

    *expriry_str = day + month + year;
    return true;
  }

  static bool DateStrFromNative(const std::string& expiry_str, std::string* date_str) {
    // invalid native format
    if (!((expiry_str.size() == 6) || (expiry_str.size() == 7))) return false;

    std::string year = expiry_str.substr(expiry_str.size() - 2);
    std::string month = expiry_str.substr(expiry_str.size() - 5, 3);
    std::string day = expiry_str.substr(0, expiry_str.size() - 5);
    if (!(std::all_of(std::begin(year), std::end(year), ::isdigit) &&
          std::all_of(std::begin(month), std::end(month), ::isupper) &&
          std::all_of(std::begin(day), std::end(day), ::isdigit))) {
      return false;
    }

    year = std::string("20") + year;
    month = MONTHS.at(month);
    if (day.size() == 1) day = "0" + day;

    *date_str = year + month + day;
    return true;
  }
};

}  // namespace coin2::exchange::deribit_futures::symbology
